﻿using HtmlAgilityPack;
using StockPredictor.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;

namespace StockPredictor.App_Start
{
    public static class YahooFinance
    {
        #region GetCurrentStockData
        /// <summary>
        /// Get current stock information using yahoo finance 
        /// </summary>
        /// <param name="symbols">array of stock symbols</param>
        public static List<CurrentStock> GetCurrentStockData(string[] symbols, string userName)
        {
            // init record List
            List<CurrentStock> records = new List<CurrentStock>();

            PortfolioRecordsContext portfolioContext = new PortfolioRecordsContext();
            IDictionary<string,string> validSymbols = new Dictionary<string,string>();
            int userId = -1;
            if(userName.Length > 0)
            {
                try
                {
                    userId = AccountUtilities.GetUserIdForUser(userName);
                }
                catch
                {
                }
            }
            var portfolioRecords = userId == -1 ? null : portfolioContext.PortfolioRecords.Where(stock => stock.UserId == userId).ToList();
            
            // trim symbols and test stock symbol through symbol lookup
            for (int i = 0; i < symbols.Length; i++)
            {
                symbols[i] = symbols[i].Trim();
                string response = GetFullCompanyName(symbols[i]);
                if (response.Length > 0) validSymbols.Add(symbols[i].ToLower(), response);
            }
            if (validSymbols.Count <= 0) return records;

            // yahoo finance url
            string yfStockFormatOptions = "nsd2ohgpvrebac";
            string yfCurrentStockUrl = "http://finance.yahoo.com/d/quotes.csv?s="
                + String.Join("+", validSymbols.Keys)
                + "&f=" + yfStockFormatOptions;
            
            try
            {
                string[] dataRows = new WebClient().DownloadString(yfCurrentStockUrl).Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
            
                // parse records
                foreach (string row in dataRows)
                {
                    // parse date info, and individual columns from row
                    CurrentStock record = new CurrentStock();
                    double value;
                    List<string> columns = new List<string>(); 
                    Regex regexObj = new Regex(@"""[^""\r\n]*""|'[^'\r\n]*'|[^,\r\n]*");
                    Match matchResults = regexObj.Match(row);
                    while (matchResults.Success)
                    {
                        if(matchResults.Value != "") columns.Add(matchResults.Value);
                        matchResults = matchResults.NextMatch();
                    }
                    // populate record
                    record.TradeDate = DateTime.Today;                    
                    record.Symbol = columns[1].Replace("/", "").Replace("\"", "").Replace("\\", "");
                    record.Name = validSymbols[record.Symbol.ToLower()];
                    if (double.TryParse(columns[3], out value)) record.Open = value;
                    if (double.TryParse(columns[4], out value)) record.High = value;
                    if (double.TryParse(columns[5], out value)) record.Low = value;
                    if (double.TryParse(columns[6], out value)) record.PreviousClose = value;
                    if (double.TryParse(columns[7], out value)) record.Volume = value;
                    if (double.TryParse(columns[8], out value)) record.PERatio = value;
                    if (double.TryParse(columns[9], out value)) record.EPS = value;
                    if (double.TryParse(columns[10], out value)) record.Bid = value;
                    if (double.TryParse(columns[11], out value)) record.Ask = value;
                    record.Change = columns[12].Replace("/", "").Replace("\"", "").Replace("\\", "").Replace(" - "," (") + ")";

                    PortfolioRecordModel prm = null;
                    if(portfolioRecords != null) prm = portfolioRecords.Find(delegate(PortfolioRecordModel e) { return e.StockSymbol == record.Symbol; });

                    if (prm == null)
                    {
                        record.InUserPortfolio = false;
                    }
                    else 
                    {
                        record.InUserPortfolio = true;
                    }

                    
                    // add to records array
                    records.Add(record);
                }
            }
            catch { }
            return records;
        }

        /// <summary>
        /// Get current stock information using yahoo finance 
        /// </summary>
        /// <param name="symbols">symbol</param>
        public static List<CurrentStock> GetCurrentStockData(string symbol, string userName)
        {
            return GetCurrentStockData(new string[] { symbol },userName);
        }
        #endregion GetCurrentStockData

        #region GetHistoricalStockData
        /// <summary>
        /// Time step for historical stock records
        /// </summary>        
        public enum intervalStep
        {
            // map keys to define intervals for historical data
            Daily,
            Weekly,
            Monthly
        }

        /// <summary>
        /// Get historical stock information using yahoo finance 
        /// </summary>
        /// <param name="symbol">stock symbol</param>
        /// <param name="startDate">start bound for historical data</param>
        /// <param name="sopDate">end bound for historical stock data</param>
        /// <param name="interval">time step for historical stock records</param>
        public static List<HistoricalStock> GetHistoricalStockData(string symbol, DateTime startDate, DateTime stopDate, intervalStep interval)
        {
            // init record List
            List<HistoricalStock> records = new List<HistoricalStock>();
            
            // trim name and verify symbol
            symbol = symbol.Trim();
            string companyName = GetFullCompanyName(symbol);
            if (companyName.Length <= 0) return records;
            
            // yahoo finance url
            string yfHistoricalUrl = "http://ichart.yahoo.com/table.csv?s=" + symbol
            + "&a=" + (startDate.Month - 1).ToString()
            + "&b=" + (startDate.Day).ToString()
            + "&c=" + (startDate.Year).ToString()
            + "&d=" + (stopDate.Month - 1).ToString()
            + "&e=" + (stopDate.Day).ToString()
            + "&f=" + (stopDate.Year).ToString()
            + "&g=" + interval
            + "&ignore=.csv";
                                    
            // get name and symbol info
            string yfStockFormatOptions = "ns";
            string yfCurrentStockUrl = "http://finance.yahoo.com/d/quotes.csv?s="
                + String.Join("+", symbol)
                + "&f=" + yfStockFormatOptions;
            
            try
            {
                string[] dataRows = new WebClient().DownloadString(yfHistoricalUrl).Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
                string[] nameSymbolInfo = new WebClient().DownloadString(yfCurrentStockUrl).Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                double[] epsVals = GetHistoricalEPS(symbol);
            
                // parse records
                double value;
           
                foreach (string row in dataRows.Skip(1))
                {
                    // parse date info, and individual columns from row
                    HistoricalStock record = new HistoricalStock();
                    string[] columns = row.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                    string[] dateCol = columns[0].Split(new string[] { "-" }, StringSplitOptions.RemoveEmptyEntries);
                    int year = System.Convert.ToInt32(dateCol[0]);
                    int month = System.Convert.ToInt32(dateCol[1]);
                    int day = System.Convert.ToInt32(dateCol[2]);

                    // populate record
                    record.TradeDate = new DateTime(year, month, day);
                    record.Name = companyName;
                    record.Symbol = nameSymbolInfo[1].Replace("/", "").Replace("\"", "").Replace("\\", "").Trim();
                    if (double.TryParse(columns[1], out value)) record.Open = value;
                    if (double.TryParse(columns[2], out value)) record.High = value;
                    if (double.TryParse(columns[3], out value)) record.Low = value;
                    if (double.TryParse(columns[4], out value)) record.Close = value;
                    if (double.TryParse(columns[5], out value)) record.Volume = value;
                    if (double.TryParse(columns[6], out value)) record.Adj_Close = value;   
                    // apply past 4 quarter EPS values
                    int dayDiff = DateTime.Today.Subtract(record.TradeDate).Days;
                    if (dayDiff < 90) record.EPS = epsVals[3];
                    else if (dayDiff < 180) record.EPS = epsVals[2];
                    else if (dayDiff < 270) record.EPS = epsVals[1];
                    else record.EPS = epsVals[0];

                    // add to records array
                    records.Insert(0,record);
                }
            }
            catch { }
            return records;
        }

        /// <summary>
        /// Get historical stock information using yahoo finance
        /// Default interval from 01/01/2007 - Current Date
        /// </summary>
        /// <param name="symbol">stock symbol</param>
        /// <param name="interval">time step for historical stock records</param>
        public static List<HistoricalStock> GetHistoricalStockData(string symbol, intervalStep interval = intervalStep.Daily)
        {
            DateTime start = new DateTime(2007,01,01);
            DateTime stop = DateTime.Today;
            List<HistoricalStock> records = GetHistoricalStockData(symbol, start, stop, interval);
            return records;
        }

        /// <summary>
        /// Get historical stock information using yahoo finance 
        /// Retrieve results for multiple stock symbols
        /// </summary>
        /// <param name="symbols">array of stock symbols</param>
        /// <param name="startDate">start bound for historical data</param>
        /// <param name="sopDate">end bound for historical stock data</param>
        /// <param name="interval">time step for historical stock records</param>
        public static List<KeyValuePair<string, List<HistoricalStock>>> GetHistoricalStockData(string[] symbols, DateTime startDate, DateTime stopDate, intervalStep interval = intervalStep.Daily)
        {
            List<KeyValuePair<string, List<HistoricalStock>>> historicalStocks = new List<KeyValuePair<string,List<HistoricalStock>>>();
            foreach (string s in symbols)
            {
                List<HistoricalStock> records = GetHistoricalStockData(s, startDate, stopDate, interval);
                KeyValuePair<string, List<HistoricalStock>> kvp = new KeyValuePair<string, List<HistoricalStock>>(s, records);
                historicalStocks.Add(kvp);
            }
            return historicalStocks;
        }

        /// <summary>
        /// Get historical stock information using yahoo finance 
        /// Default interval from 01/01/2007 - Current Date
        /// Retrieve results for multiple stock symbols       
        /// </summary>
        /// <param name="symbols">array of stock symbols</param>
        /// <param name="symbol">stock symbol</param>
        /// <param name="interval">time step for historical stock records</param>
        public static List<KeyValuePair<string, List<HistoricalStock>>> GetHistoricalStockData(string[] symbols, intervalStep interval = intervalStep.Daily)
        {
            DateTime start = new DateTime(2007, 01, 01);
            DateTime stop = DateTime.Today;
            List<KeyValuePair<string, List<HistoricalStock>>> historicalStocks = new List<KeyValuePair<string, List<HistoricalStock>>>();
            foreach (string s in symbols)
            {
                List<HistoricalStock> records = GetHistoricalStockData(s, start, stop, interval);
                KeyValuePair<string, List<HistoricalStock>> kvp = new KeyValuePair<string, List<HistoricalStock>>(s, records);
                historicalStocks.Add(kvp);
            }
            return historicalStocks;
        }

        /// <summary>
        /// Get historical stock information using yahoo finance 
        /// Retrieve results for multiple stock symbols as appended list
        /// </summary>
        /// <param name="symbols">array of stock symbols</param>
        /// <param name="startDate">start bound for historical data</param>
        /// <param name="sopDate">end bound for historical stock data</param>
        /// <param name="interval">time step for historical stock records</param>
        public static List<HistoricalStock> GetHistoricalStockDataList(string[] symbols, DateTime startDate, DateTime stopDate, intervalStep interval = intervalStep.Daily)
        {
            List<HistoricalStock> hs = new List<HistoricalStock>();
            List<KeyValuePair<string, List<HistoricalStock>>> ds = GetHistoricalStockData(symbols, startDate, stopDate, interval);
            foreach (KeyValuePair<string, List<HistoricalStock>> kvp in ds)
            {
                hs.AddRange(kvp.Value);
            }
            return hs;
        }

        /// <summary>
        /// Get historical stock information using yahoo finance 
        /// Retrieve results for multiple stock symbols as appended list
        /// Default interval from 01/01/2007 - Current Date
        /// </summary>
        /// <param name="symbols">array of stock symbols</param>
        /// <param name="interval">time step for historical stock records</param>
        public static List<HistoricalStock> GetHistoricalStockDataList(string[] symbols, intervalStep interval = intervalStep.Daily)
        {
            DateTime start = new DateTime(2007,01,01);
            DateTime stop = DateTime.Today;
            List<HistoricalStock> hs = new List<HistoricalStock>();
            List<KeyValuePair<string, List<HistoricalStock>>> ds = GetHistoricalStockData(symbols, start, stop, interval);
            foreach (KeyValuePair<string, List<HistoricalStock>> kvp in ds)
            {
                hs.AddRange(kvp.Value);
            }
            return hs;
        }
        #endregion GetHistoricalStockData

        #region Helpers

        public static string GetFullCompanyName(string stockSymbol)
        {
            string companyName = "";

            string url = "http://www.google.com/finance/info?infotype=infoquoteall&q=" + stockSymbol;

            string responseString = getResponseString(url);
            if (responseString.Length > 0)
            {
                string[] columns = responseString.Split(new string[] { "," }, StringSplitOptions.None);
                for (int i = 0; i < columns.Length; i++)
                {
                    if (columns[i].Contains("name"))
                    {
                        companyName = columns[i].Replace("/", "").Replace("\"", "").Replace("\\", "").Replace("\n", "").Replace("name : ", "");
                    }
                }
            }

            return companyName;
        }

        public static double[] GetHistoricalEPS(string stockSymbol)
        {
            // ref: http://finance.yahoo.com/q/ae?s=yhoo+Analyst+Estimates
            // get EPS values for past 4 quarters using Yahoo finance
            double[] epsVals = new double[4];
            string epsURL = "http://finance.yahoo.com/q/ae?s=" + stockSymbol + "+Analyst+Estimates";
            HtmlDocument doc = new HtmlDocument();
            doc.Load(new WebClient().OpenRead(epsURL));
            HtmlNode pageDiv = doc.DocumentNode.ChildNodes["html"].ChildNodes["body"].SelectSingleNode("//div[@id='screen']");
            HtmlNode epsRow = pageDiv.SelectSingleNode("//div[@id='rightcol']").SelectSingleNode("//table[@id='yfncsumtab']")
                .Descendants().Where(node => node.InnerText == "EPS Actual").FirstOrDefault();
            epsVals[0] = System.Convert.ToDouble(epsRow.NextSibling.InnerText != "N/A" ? epsRow.NextSibling.InnerText : "0.0");
            epsVals[1] = System.Convert.ToDouble(epsRow.NextSibling.NextSibling.InnerText != "N/A" ? epsRow.NextSibling.NextSibling.InnerText : "0.0");
            epsVals[2] = System.Convert.ToDouble(epsRow.NextSibling.NextSibling.NextSibling.InnerText != "N/A" ? epsRow.NextSibling.NextSibling.NextSibling.InnerText : "0.0");
            epsVals[3] = System.Convert.ToDouble(epsRow.NextSibling.NextSibling.NextSibling.NextSibling.InnerText != "N/A" ? epsRow.NextSibling.NextSibling.NextSibling.NextSibling.InnerText : "0.0");

            return epsVals;
        }
        
        /// <summary>
        /// Return web request response as string
        /// </summary>
        /// <param name="requestString">web request url string</param>
        private static string getResponseString(string requestString)
        {
            try
            {
                return new WebClient().DownloadString(requestString);
            }
            catch
            {
                return String.Empty;
            }
        }
        #endregion Helpers
    }    
}